/*
 * Created on Sep 21, 2004
 *
 */
package jiggle;

/**
 * AbstractForceLaw: Force's with common functionality:
 * - all have a preferred edge length
 * - they work with pairs of coordinates, and weights depend on just the current 
 * distance and the preferred distances (defined by each forces abstract method pairwiseWeights)
 * - by default the pairs of coordinates are based on all the edges
 * - support for repulsion comes from negative weights provided by pairwiseWeights
 * - note: distances calculation method are set by the graph, they can be either be external distances 
 * or internal distances(preferred distance=preferred edge length + the sumOfRadii)
 * 
 * @author vineet
 */
public abstract class AbstractForceLaw extends ForceLaw {

	public double preferredEdgeLength;
	
	protected AbstractForceLaw(Graph g, double edgeLength) {
		super(g);
        preferredEdgeLength = edgeLength;
	}

	public double pairwiseWeights (Cell c1, Cell c2) {
	    return graph.pairwiseWeights(this, c1, c2);
	}

	public double pairwiseWeights (Cell cell, double [] coords) {
	    return graph.pairwiseWeights(this, cell, coords);
	}


	/**
	 * pair wise weights gived preferred and current distance
	 * @param pref - preferred distance between edges of cells (not between centeres of cells)
	 * @param curr - current distance between centeres of cells
	 * @return
	 */
	public abstract double pairwiseWeights (double pref, double curr);

	@Override
    public void apply (double [][] negativeGradient) {
        applyForEdges(negativeGradient);
    }
		
    /**
     * Applies the forces on all edges in the graph
     * 
     * @param negativeGradient
     */
    protected void applyForEdges (double [][] negativeGradient) {
        for (Edge e : graph.edges) {                // for every edge
            applyForCells(negativeGradient, e);
        }
	}

    /**
     * Applies the forces on all pairs of vertices in the graph
     * 
     * @param negativeGradient
     */
	protected void applyForVertexPairs (double [][] negativeGradient) {
        int n = graph.getNumberOfVertices();
        for (int i = 0; i < n - 1; i++) {
            Cell v1 = graph.vertices.get(i);
            if (!(v1 instanceof Vertex)) continue;
            
            for (int j = i + 1; j < n; j++) {
                Cell v2 = graph.vertices.get(i);
                if (!(v2 instanceof Vertex)) continue;
                
                applyForCells(negativeGradient, v1, v2);
            }
        }
	}

    protected void applyForCells(double[][] negativeGradient, Edge e) {
		applyForCells(negativeGradient, e.getFrom(), e.getTo());
	}
	
	/**
	 * If force is not be applied on either cell then it's gradientNdx must be -1 
     * 
	 * @param negativeGradient
	 * @param from
	 * @param to
	 */
	public void applyForCells(double[][] negativeGradient, Cell from, Cell to) {
	    int d = graph.getDimensions();

	    double fromCoords[] = from.getCoords();
        double toCoords[] = to.getCoords();
	    
        double w = pairwiseWeights(from, to);
        w = Math.min(w, cap / Cell.getDistance(from, to));
        w = -Math.min(-w, cap / Cell.getDistance(from, to));
        for (int j = 0; j < d; j++) {								// per dimension
            double force = (toCoords[j] - fromCoords[j]) * w;
            if (from.gradientNdx != -1)
                negativeGradient[from.gradientNdx][j] += force * to.getWeight();
            if (to.gradientNdx != -1)
                negativeGradient[to.gradientNdx][j] -= force * from.getWeight();
        }
    }


    /**
     * Similar to applyForCells but modified to work with a coord 
     */
    public void applyForCellCoord(double[][] negativeGradient, Cell from, double toCoords[], double toWeight) {
        int d = graph.getDimensions();

        double fromCoords[] = from.getCoords();
        
        double w = pairwiseWeights(from, toCoords);
        w = Math.min(w, cap / Cell.getDistance(from, toCoords));
        w = -Math.min(-w, cap / Cell.getDistance(from, toCoords));
        for (int j = 0; j < d; j++) {                               // per dimension
            double force = (toCoords[j] - fromCoords[j]) * w;
            if (from.gradientNdx != -1)
                negativeGradient[from.gradientNdx][j] += force * toWeight;
        }
    }

}
